import Utils from "../../Script/Utils";

const { ccclass, property } = cc._decorator;

@ccclass
export default class ScrollViewCtrl extends cc.Component {

    @property(cc.Prefab)
    itemPrefab: cc.Prefab = null;

    bindIndexList: {};
    scrollView: cc.ScrollView = null;
    content: cc.Node = null;
    view: cc.Node = null;
    layout: cc.Layout = null;
    itemName: string = null;
    mat4: cc.Mat4 = null;
    isInit: boolean = null;
    data: any[] = null;
    callbackList: any[] = null;
    extData: any = null;
    firstX: number = null;
    firstY: number = null;
    itemCache: any[] = null;
    itemBuffer: any = null;
    _tmpV2: any = null;
    viewRect: any = null;

    onLoad() {
        this.initOnce();
    }

    initOnce() {
        this.bindIndexList = {};    /**记录哪个index需要绑定Item */
        this.scrollView = this.node.getComponent(cc.ScrollView);
        this.content = this.scrollView.content
        this.view = this.content.parent;
        this.layout = this.content.getComponent(cc.Layout);
        this.itemName = this.itemPrefab.name;
        this.content.on(cc.Node.EventType.POSITION_CHANGED, this.scrollEvent, this);
        this.mat4 = cc.mat4();
        this.initOnce = function () { }
    }

    /**注册滚动事件 */
    registerScrollEvent(handler, target) {
        if (!handler.name) {
            return;
        }
        var scrollView = this.node.getComponent(cc.ScrollView);
        var eventHandler = new cc.Component.EventHandler();
        eventHandler.target = target.node;
        eventHandler.component = cc.js.getClassName(target);
        eventHandler.handler = handler.name;
        var index = scrollView.scrollEvents.length;
        scrollView.scrollEvents[index] = eventHandler;
    }

    /**
     * 初始化数据和表现
     * @param {Array} data 所有的列表数据组成的数组
     * @param {any} param 要传递到item的额外参数
     */
    init(data, param: any = {}) {
        this.initOnce();
        if (!Array.isArray(data)) {
            console.error("传进来的数据不为数组！");
            return;
        }
        this.isInit = true;
        this.data = data;
        this.callbackList = [];
        this.extData = param.extData;
        if (param.onChanged) {
            this.onItemChanged(param.onChanged);
        }
        this.layout.enabled = false;
        this.scrollView.stopAutoScroll();

        /**模板 */
        var template = this.itemPrefab.data;

        var paddingLeft = this.layout.paddingLeft;      /**左边距 */
        var paddingRight = this.layout.paddingRight;    /**右边距 */
        var paddingTop = this.layout.paddingTop;        /**上边距 */
        var paddingBottom = this.layout.paddingBottom;  /**下边距 */
        var spacingX = this.layout.spacingX;            /**水平间隔距离 */
        var spacingY = this.layout.spacingY;            /**垂直间隔距离 */

        let firstX = template.x;
        let firstY = template.y;

        if (this.scrollView.horizontal) {
            firstX = -template.width / 2
            firstX -= paddingLeft;
        }
        if (this.scrollView.vertical) {
            firstY = -template.height / 2;
            firstY -= paddingTop;
        }
        this.firstX = firstX;
        this.firstY = firstY;

        this.itemCache = [];
        this.itemBuffer = this.itemBuffer || [];

        this.itemBuffer.forEach(buffer => {
            buffer.index = -1;
            buffer.item.x = -9999999;
            buffer.item.y = -9999999;
            buffer.item.opacity = 1;
        });

        var initCache = (i) => {
            this.itemCache[i] = this.itemCache[i] || {};
            this.itemCache[i].x = firstX;
            this.itemCache[i].y = firstY;
            this.itemCache[i].width = template.width;
            this.itemCache[i].height = template.height;
            this.itemCache[i].scaleX = template.scaleX;
            this.itemCache[i].scaleY = template.scaleY;
            this.itemCache[i].visible = false;
        }
        initCache(0);
        for (let i = 1; i < this.data.length; i++) {
            initCache(i);
            if (this.scrollView.horizontal) {
                this.itemCache[i].x = this.itemCache[i - 1].x - (this.itemCache[i - 1].width / 2 + this.itemCache[i].width / 2 + spacingX);
            }
            if (this.scrollView.vertical) {
                this.itemCache[i].y = this.itemCache[i - 1].y - (this.itemCache[i - 1].height / 2 + this.itemCache[i].height / 2 + spacingY);
            }
        }
        let lastItem = this.itemCache[this.itemCache.length - 1];
        if (this.scrollView.horizontal) {
            this.content.width = Math.abs(lastItem.x + lastItem.width / 2 + paddingRight);
        }
        if (this.scrollView.vertical) {
            this.content.height = Math.abs(lastItem.y - lastItem.height / 2 - paddingBottom);
        }
        this.updateListView();
    }

    updateItemView(item, index) {
        var js = Utils.Instance.getScript(item);
        if (js && js.setData) {
            js.setData(this.data[index], index, this.extData);
            js.updateView && js.updateView();
        }
    }

    getItem() {
        let item: cc.Node = cc.instantiate(this.itemPrefab);
        item.x = this.firstX;
        item.y = this.firstY;
        let data = {
            item: item,
            index: -1,
        }
        this.itemBuffer.push(data);
        item.on(cc.Node.EventType.SIZE_CHANGED, this.onItemSizeChanged.bind(this, item), this);
        item.on(cc.Node.EventType.SCALE_CHANGED, this.onItemSizeChanged.bind(this, item), this);
        return data;
    }

    scrollEvent() {
        if (!this.content || !this.isInit) return;
        this.updateListView();
    }

    updateListView() {
        if (!this.itemCache) {
            return;
        }
        var attach = (buffer, i) => {
            buffer.index = i;
            buffer.item.x = this.itemCache[i].x;
            buffer.item.y = this.itemCache[i].y;
            buffer.item.scaleX = this.itemCache[i].scaleX;
            buffer.item.scaleY = this.itemCache[i].scaleY;
            buffer.item.opacity = 255;
            if (this.scrollView.horizontal) {
                buffer.item.width = this.itemCache[i].width;
            }
            if (this.scrollView.vertical) {
                buffer.item.height = this.itemCache[i].height;
            }
            buffer.item.parent = this.content;
            this.updateItemView(buffer.item, i);
        }
        for (var i = 0; i < this.itemCache.length; i++) {
            var cache = this.itemCache[i];
            var visible = this.isItemInView(i);
            var buffer = this.itemBuffer.find((v) => {
                return v.index == i;
            });
            if (visible) {
                if (this.bindIndexList[i]) {
                    buffer = this.itemBuffer.find((v) => {
                        return v.bindIndex == i;
                    });
                }
                if (!buffer) {
                    buffer = this.itemBuffer.find((v) => {
                        return v.index == -1 && v.bindIndex == undefined;
                    });
                    buffer = buffer || this.getItem();
                }
                if (buffer.index != i) {
                    attach(buffer, i);
                }
            } else if (buffer) {
                buffer.index = -1;
                buffer.item.x = -9999999;
                buffer.item.y = -9999999;
                buffer.item.opacity = 1;
            }
            if (cache.visible != visible) {
                this.runItemChangedCallback(i, visible);
            }
            cache.visible = visible;
        }

        /**调整层级 */
        this.itemBuffer.sort((a, b) => {
            if (a.index < 0 || b.index < 0) {
                return 1;
            }
            return a.index - b.index;
        });
        for (var i = 0; i < this.itemBuffer.length; i++) {
            this.itemBuffer[i].item.setSiblingIndex(i);
        }
    }

    /**item是否在可视区域内 */
    isItemInView(index) {
        this._tmpV2 = this._tmpV2 || cc.v2(0, 0);
        this.view.getWorldMatrix(this.mat4);
        let scale = this.mat4.m[0];
        let wposx = this.mat4.m[12];
        let wposy = this.mat4.m[13];

        let width1 = this.view.width * scale;
        let height1 = this.view.height * scale;

        let wpos = this.view.convertToWorldSpaceAR(cc.Vec2.ZERO, this._tmpV2);
        if (!this.viewRect || scale != 1 || (this.viewRect.x + width1 / 2) != wposx || (this.viewRect.y + height1 / 2) != wposy) {
            this.viewRect = new cc.Rect(wpos.x - width1 / 2, wpos.y - height1 / 2, width1, height1);
        }
        let data = this.itemCache[index];
        let wpos2 = this.content.convertToWorldSpaceAR(cc.v2(data.x, data.y));

        let width2 = data.width * data.scaleX;
        let height2 = data.height * data.scaleY;
        let rect = new cc.Rect(wpos2.x - width2 / 2, wpos2.y - height2 / 2, width2, height2);
        let ret = this.viewRect.intersects(rect);
        return ret;
    }

    onItemSizeChanged(item) {
        if (!this.itemCache) {
            return;
        }
        let data = this.itemBuffer.find((v) => {
            return v.item == item;
        });
        if (data && data.index >= 0) {
            var itemData = this.itemCache[data.index];
            if (this.scrollView.horizontal && itemData.width == item.width && itemData.scaleX == item.scaleX) {
                return;
            }
            if (this.scrollView.vertical && itemData.height == item.height && itemData.scaleY == item.scaleY) {
                return;
            }
            itemData.width = item.width;
            itemData.scaleX = item.scaleX;

            itemData.height = item.height;
            itemData.scaleY = item.scaleY;

            this.updateBuffer();
            this.updateListView();
        }
    }

    setItemProperty(index, property, value) {
        if (!this.itemCache || !this.itemBuffer) {
            return;
        }
        var buffer = this.itemBuffer.find(v => {
            return v.index == index;
        });
        if (buffer) {
            buffer.item[property] = value;
        } else {
            var itemData = this.itemCache[index];
            itemData[property] = value;
        }
        this.updateBuffer();
        this.updateListView();
    }

    updateBuffer() {
        let lastItem = this.itemCache[this.itemCache.length - 1];
        if (this.scrollView.vertical) {

            this.itemCache[0].y = -this.itemCache[0].height / 2 - this.layout.paddingTop;
            if (this.itemCache[0].scaleY != 1) {
                this.itemCache[0].y = -Math.abs(this.itemCache[0].scaleY * this.itemCache[0].height) / 2 - this.layout.paddingTop;
            }
            this.itemBuffer.find((v) => {
                if (v.index == 0) {
                    v.item.y = this.itemCache[0].y;
                }
            });
            for (var i = 1; i < this.data.length; i++) {
                var data1 = this.itemCache[i - 1];
                var data2 = this.itemCache[i];
                var h1 = data1.height;
                var h2 = data2.height;

                if (data1.scaleY != 1) {
                    h1 = Math.abs(data1.scaleY * data1.height);
                }
                if (data2.scaleY != 1) {
                    h2 = Math.abs(data2.scaleY * data2.height);
                }

                data2.y = data1.y - (h1 / 2 + h2 / 2 + this.layout.spacingY);
                this.itemBuffer.find((v) => {
                    if (v.index == i) {
                        v.item.y = data2.y;
                    }
                });
            }
            var lastRealHeight = lastItem.height / 2;
            if (lastItem.scaleY != 1) {
                lastRealHeight = Math.abs(lastItem.scaleY * lastItem.height) / 2;
            }
            this.content.height = Math.abs(lastItem.y - lastRealHeight - this.layout.paddingBottom);
        }
    }

    /**
     * 滑动到指定Item位置
     * @param {*} index 
     */
    scrollToItem(index, t, extParams) {
        if (!this.itemCache) {
            return;
        }
        if (index < 0) index = 0;
        if (index >= this.itemCache.length) index = this.itemCache.length - 1;

        var cache = this.itemCache[index];
        if (!cache) {
            return;
        }
        extParams = extParams || {};
        t = t || 0;
        if (this.scrollView) {
            var toY;
            if (extParams.customTween) {
                // toY = -(cache.y + Math.abs(cache.height * cache.scaleY) / 2);
                // this.content.runAction(cc.moveTo(t, cc.v2(0, toY)));
            } else {
                toY = -(cache.y + Math.abs(cache.height * cache.scaleY) / 2);
                this.scrollView.scrollToOffset(cc.v2(0, toY), t)
            }
        }
    }

    /**
     * 获取Item
     */
    getItemByIndex(index) {
        if (!this.itemBuffer) {
            return;
        }
        var buffer = this.itemBuffer.find(v => {
            return v.index == index;
        }) || {};
        return buffer.item;
    }

    removeItemByIndex(index) {
        if (!this.itemBuffer) {
            return;
        }
        var idx = this.itemBuffer.findIndex(v => {
            return v.index == index;
        });
        if (idx >= 0) {
            var buffer = this.itemBuffer.splice(idx, 1);
            buffer.item.destroy();
        }
    }

    /**
     * 下标和Item绑定
     * item只能指定index, item将不可复用
     */
    bindItemWithIndex(item, bindIndex, isBind) {
        if (!this.itemBuffer) {
            return;
        }
        isBind = isBind || true;
        var buffer = this.itemBuffer.find(v => {
            return v.item == item;
        });
        if (buffer) {
            buffer.bindIndex = bindIndex;
            if (isBind) {
                this.bindIndexList[bindIndex] = isBind;
            } else {
                delete this.bindIndexList[bindIndex];
                delete buffer.bindIndex;
            }
        }
    }

    onItemChanged(callback) {
        if (typeof (callback) == "function") {
            this.callbackList.push(callback);
        }
    }

    runItemChangedCallback(index, visible) {
        try {
            for (let i = 0; i < this.callbackList.length; i++) {
                this.callbackList[i](index, visible);
            }
        } catch (e) {
            console.error(e);
        }
    }

    recycle() {
        if (this.itemBuffer) {
            this.itemBuffer.forEach(element => {
                element.item.destroy();
            });
        }
        this.itemCache = null;
        this.itemBuffer = null;
    }


}
